use crate::try_curl;
use crate::util::{get_json_i64, get_json_str};
use crate::utils::create_easy;
use alloc::borrow::ToOwned;
use alloc::format;
use alloc::str;
use alloc::string::String;
#[cfg(feature = "json_c")]
use alloc::string::ToString;
use alloc::vec::Vec;
#[cfg(feature = "json_c")]
use embed_std::cstr_ptr;
use embed_std::infoln;
use embed_std::{Error, Result};

mod capi;

pub const PROTO_HTTP: &str = "http";
pub const PROTO_HTTPS: &str = "https";
pub const PROTO_MQTT: &str = "mqtt";
pub const PROTO_MQTTS: &str = "mqtts";
pub const PROTO_NTP: &str = "ntp";
pub const PROTO_COAP: &str = "coap";
pub const PROTO_UKEEP: &str = "ukeep";
pub const GSLB: &str = "gslb.iot.pesipet.com";

const DEFAULT_APP_ID: &str = "eIoTLink";
const DEFAULT_LANG: &str = "zh-cn";

#[derive(Debug)]
pub struct Server {
    pub ip: String,
    pub domain: String,
    pub protocols: String,
    pub priority: u8,
    pub server_type: u8,
}

impl Server {
    pub fn addr(&self) -> &str {
        if !self.domain.is_empty() {
            self.domain.as_str()
        } else {
            self.ip.as_str()
        }
    }
    pub fn protocol(&self) -> &str {
        let items: Vec<&str> = self.protocols.split(";").collect();
        if items.len() > 0 {
            items[0]
        } else {
            self.protocols.as_str()
        }
    }
    pub fn support_proto(&self, proto: &str) -> bool {
        self.protocols.find(proto).is_some()
    }

    pub fn priority(&self) -> u8 {
        self.priority
    }
}

#[cfg(feature = "json_rs")]
fn parse_value(v: &serde_json::Value) -> Result<Server> {
    if let Some(v) = v.as_object() {
        let domain = v
            .get("svr_domain")
            .ok_or("no svr_domain".to_owned())?
            .as_str()
            .ok_or("invalid svr_domain")?;
        let ipv4 = v
            .get("svr_ipv4")
            .ok_or("no svr_ipv4".to_owned())?
            .as_str()
            .ok_or("invalid svr_ipv4")?;
        let ipv6 = v
            .get("svr_ipv6")
            .ok_or("no svr_ipv6".to_owned())?
            .as_str()
            .ok_or("invalid svr_ipv6")?;
        let protocols = v
            .get("protocols")
            .ok_or("no protocols".to_owned())?
            .as_str()
            .ok_or("invalid protocols")?;
        let priority = v
            .get("priority")
            .ok_or("no priority".to_owned())?
            .as_u64()
            .ok_or("invalid priority")? as u8;
        let server_type = v
            .get("server_type")
            .ok_or("no server_type".to_owned())?
            .as_u64()
            .ok_or("invalid server_type")? as u8;
        let addr = if !ipv4.is_empty() { ipv4 } else { ipv6 };
        return Ok(Server {
            ip: addr.to_owned(),
            domain: domain.to_owned(),
            protocols: protocols.to_owned(),
            priority,
            server_type,
        });
    }
    Err(Error::InvalidJsonString(format!("{:?}", v)))
}

#[cfg(feature = "json_rs")]
pub fn parse_addrs(data: &[u8]) -> Result<Vec<Server>> {
    let root: serde_json::Value = serde_json::from_slice(data).map_err(|e| format!("{}", e))?;
    if let Some(root) = root.as_object() {
        let rc = root.get("rc").ok_or("no rc".to_owned())?;
        if !rc.is_number() || rc.as_i64() != Some(0) {
            return Err(Error::InvalidJsonString(format!("{:?}", root)));
        }
        let data = root.get("data").ok_or("no data".to_owned())?;
        if let Some(data) = data.as_object() {
            let mut servers = Vec::new();
            let dev_gws = data.get("dev_gws").ok_or("no dev_gws")?;
            if let Some(dev_gws) = dev_gws.as_array() {
                for svr in dev_gws.iter() {
                    let addr = parse_value(&svr)?;
                    servers.push(addr);
                }
            }
            let api_gws = data.get("api_gws").ok_or("no api_gws")?;
            if let Some(api_gws) = api_gws.as_array() {
                for svr in api_gws.iter() {
                    let addr = parse_value(&svr)?;
                    servers.push(addr);
                }
            }
            return Ok(servers);
        }
    }
    Err(Error::InvalidJsonString(format!("{:?}", root)))
}

#[cfg(feature = "json_c")]
fn parse_value(v: &cjson::RefValue) -> Result<Server> {
    if !v.is_object() {
        return Err(Error::InvalidJsonField("invalid server item".to_owned()));
    }
    let domain = get_json_str(&v, "svr_domain")?;
    let ipv4 = get_json_str(&v, "svr_ipv4")?;
    let ipv6 = get_json_str(&v, "svr_ipv6")?;
    let protocols = get_json_str(&v, "protocols")?;
    let priority = get_json_i64(&v, "priority")? as u8;
    let server_type = get_json_i64(&v, "server_type")? as u8;
    let addr = if !ipv4.is_empty() { ipv4 } else { ipv6 };
    return Ok(Server {
        ip: addr,
        domain,
        protocols,
        priority,
        server_type,
    });
}

#[cfg(feature = "json_c")]
pub fn parse_addrs(data: &[u8]) -> Result<Vec<Server>> {
    let json = str::from_utf8(data).map_err(|e| Error::Other(e.to_string()))?;
    let root = cjson::Json::parse(json)?;
    if root.is_object() {
        let rc = get_json_i64(&root, "rc")? as i32;
        if rc != 0 {
            return Err(Error::Io(
                rc,
                get_json_str(&root, "rd").unwrap_or("failed".to_owned()),
            ));
        }
        let data = root
            .get(cstr_ptr!("data"))
            .ok_or(Error::NoJsonField("data".to_owned()))?;
        if !data.is_object() {
            return Err(Error::InvalidJsonField("data".to_owned()));
        }
        let mut servers = Vec::new();
        let dev_gws = data
            .get(cstr_ptr!("dev_gws"))
            .ok_or(Error::NoJsonField("dev_gws".to_owned()))?;
        if dev_gws.is_array() {
            for i in 0..dev_gws.get_item_num() {
                if let Some(item) = dev_gws.get_item(i) {
                    let addr = parse_value(&item)?;
                    servers.push(addr);
                }
            }
        }
        if let Some(api_gws) = data.get(cstr_ptr!("api_gws")) {
            if api_gws.is_array() {
                for i in 0..api_gws.get_item_num() {
                    if let Some(item) = api_gws.get_item(i) {
                        let addr = parse_value(&item)?;
                        servers.push(addr);
                    }
                }
            }
        }
        if let Some(keeps) = data.get(cstr_ptr!("dev_keeps")) {
            if keeps.is_array() {
                for i in 0..keeps.get_item_num() {
                    if let Some(item) = keeps.get_item(i) {
                        let addr = parse_value(&item)?;
                        servers.push(addr);
                    }
                }
            }
        }
        return Ok(servers);
    }
    Err(Error::InvalidJson)
}

pub fn get_servers(host: &str, product_code: &str, device_uid: &str) -> Result<Vec<Server>> {
    get_servers_v2(host, product_code, device_uid, 0x1 | 0x2)
}

pub fn get_servers_v2(
    host: &str,
    product_code: &str,
    device_uid: &str,
    flags: u32,
) -> Result<Vec<Server>> {
    let mut buffer: Vec<u8> = Vec::new();
    {
        let url = format!(
            "http://{}/v2/gslb/products/{}/devices/{}/servers?flags={}",
            host, product_code, device_uid, flags
        );
        let mut curl = create_easy(url.as_str())?;
        infoln!("gslb url {}", url);
        try_curl!(curl.url(url.as_str()))?;
        let mut headers = curl::easy::List::new();
        try_curl!(headers.append("Content-Type: Application/json;charset=UTF-8"))?;
        let lang = format!("X-Lang: {}", DEFAULT_LANG);
        try_curl!(headers.append(lang.as_str()))?;
        let app_id = format!("X-App: {}", DEFAULT_APP_ID);
        try_curl!(headers.append(app_id.as_str()))?;
        try_curl!(curl.http_headers(headers))?;
        let _ = curl.get(true);
        let mut handle = curl.transfer();
        try_curl!(handle.write_function(|data| {
            buffer.extend(data);
            Ok(data.len())
        }))?;
        try_curl!(handle.perform())?;
    }
    infoln!("{}", String::from_utf8_lossy(buffer.as_slice()));
    parse_addrs(buffer.as_slice())
}

pub fn select_server<'a>(servers: &'a Vec<Server>, proto: &str) -> Option<&'a Server> {
    for svr in servers.iter() {
        if svr.support_proto(proto) {
            return Some(svr);
        }
    }
    None
}
